import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:archive/archive_io.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:todo_app/controllers/RtcRoomController.dart';
import 'package:todo_app/controllers/definitions/RtcRoomModel.dart';
import 'package:todo_app/utils/call/CallHelper.dart';
import 'package:todo_app/utils/call/contexts/IdleContext.dart';
import 'package:todo_app/utils/webrtc/RTCMessageEventBus.dart';
import 'package:todo_app/utils/webrtc/RTCMessageModel.dart';

RTCPeerConnectionController useRTCPeerConnectionController(RtcRoomModel room) {
  return use( _RTCPeerConnectionControllerHook(room));
}

class _RTCPeerConnectionControllerHook extends Hook<RTCPeerConnectionController> {
  const _RTCPeerConnectionControllerHook(this.room);
  final RtcRoomModel room;

  @override
  HookState<RTCPeerConnectionController, Hook<RTCPeerConnectionController>> createState() {
    return _RTCPeerConnectionControllerHookState();
  }
}

typedef SetStateFunction = void Function(VoidCallback fn);

class _RTCPeerConnectionControllerHookState extends HookState<RTCPeerConnectionController, _RTCPeerConnectionControllerHook> {
  late RTCPeerConnectionController controller;
  
  @override
  void initHook() {
    super.initHook();
    controller = RTCPeerConnectionController();
    controller.room = hook.room;
    controller.setState = (VoidCallback fn) {
      setState(fn);
    };
    controller.init();
  }

  @override
  RTCPeerConnectionController build(BuildContext context) {
    return controller;
  }

  @override
  void dispose() {
    controller.dispose();
    var roomId = hook.room.room_id!;
    CallHelper.sendRtcMsgByExit(roomId, CloseReason.USER_EXIT);
    RtcRoomController.joinRoomUsingPut(roomId);
    super.dispose();
  }
}

class RTCPeerConnectionController {
  final renderer = RTCVideoRenderer();
  late SetStateFunction setState;
  late RtcRoomModel room;
  late RTCPeerConnection pc;
  RTCDataChannel? dataChannel;
  RTCDataChannelState? dataChannelState;
  
  Function(MediaStream stream)? onAddStream;
  Function(MediaStream stream)? onRemoveStream;
  Function(MediaStream stream, MediaStreamTrack track)? onAddTrack;
  Function(MediaStream stream, MediaStreamTrack track)? onRemoveTrack;
  Function(RTCPeerConnection pc)? onOfferReceived;
  Function(RTCPeerConnection pc)? onAnswerReceived;
  Function(RTCTrackEvent event)? onTrack;
  Function(RTCSignalingState state)? onSignalingState;
  Function(RTCPeerConnectionState state)? onConnectionState;
  Function(RTCIceGatheringState state)? onIceGatheringState;
  Function(RTCIceConnectionState state)? onIceConnectionState;
  Function(RTCDataChannelMessage data)? onChannelDataMessage;
  Function(RTCDataChannelState state)? onDataChannelState;
  // Function(RTCPeerConnection pc)? onRTCPeerConnectionCreated;
  late StreamSubscription subscription;

  var servers = {
    'iceServers': [
      {'urls': ['stun:123.249.34.189:3478'],'username': 'coturn','credential': 'coturn'},
      {'urls': ['turn:123.249.34.189:3478'],'username': 'coturn','credential': 'coturn'}
    ],
    'iceTransportPolicy': 'all',
    'rtcpMuxPolicy': 'require',
    'iceCandidatePoolSize': '0'
  };
  var constraints = {'optional': [{'googIPv6': false}]};

  Future<RTCPeerConnection> createPc() async {
    pc = await createPeerConnection(servers, constraints);
    pc.onIceCandidate = (candidate) {
      RTCMessageEventBus.sendRtcMessage(RTCMsgType.candidate, room.user_id!, room.room_id!, candidate.toMap());
    };
    pc.onRenegotiationNeeded = ()=> createOffer();
    pc.onTrack = onTrack;
    pc.onAddTrack = onAddTrack;
    pc.onRemoveTrack = onRemoveTrack;
    pc.onAddStream = onAddStream;
    pc.onRemoveStream = onRemoveStream;
    pc.onSignalingState = onSignalingState;
    pc.onConnectionState = onConnectionState;
    pc.onIceGatheringState = onIceGatheringState;
    pc.onIceConnectionState = onIceConnectionState;
    pc.onDataChannel = (channel) {
      dataChannel = channel;
      dataChannel!.onDataChannelState = (state) {
        print(state);
        dataChannelState = state;
        if(onDataChannelState != null) onDataChannelState!(state);
      };
      dataChannel!.onMessage = (data) {
        print(data.text);
        if(onChannelDataMessage!= null) onChannelDataMessage!(data);
        channel.send(RTCDataChannelMessage('回复的消息'));
      };
    };
    return pc;
  }

  init() async {
    subscription = RTCMessageEventBus.eventBus.on<RTCMessageResponse>().asBroadcastStream().listen((event) {
      if(event.type == RTCMessageResponseType.receivedMsg) {
        var msg = event.payload;
        if(msg.type == RTCMsgType.exitRoom) {
          // 有用户主动退出
          CallHelper.endCall(closeReason: CloseReason.USER_EXIT, roomId: msg.room!);
        }
        if(msg.type == RTCMsgType.candidate) {
          var candidate = RTCIceCandidate(msg.payload["candidate"], msg.payload["sdpMid"], msg.payload["sdpMLineIndex"]);
          pc.addCandidate(candidate);
        }
        if(msg.type == RTCMsgType.joinRoom) {
          maybeSendOffer();
        }
        if(msg.type == RTCMsgType.offer) {
          var offerJson = RTCMessageEventBus.gzipDencode(msg.payload as String);
          var offer = jsonDecode(offerJson) as Map;
          var description = RTCSessionDescription(offer["sdp"], offer["type"]);
          pc.setRemoteDescription(description);
          if(onOfferReceived != null) onOfferReceived!(pc);
        }
        if(msg.type == RTCMsgType.answer) {
          var answerJson = RTCMessageEventBus.gzipDencode(msg.payload as String);
          var answer = jsonDecode(answerJson) as Map;
          var description = RTCSessionDescription(answer["sdp"], answer["type"]);
          pc.setRemoteDescription(description);
          if(onAnswerReceived != null) onAnswerReceived!(pc);
        }
      }
    });
  }
  // 都已经准备好后 开始发送准备好的消息
  Future sendJoinedRoomMsg() async {
    var roomId = room.room_id!;
    CallHelper.sendRtcMsg(RTCMsgType.joinRoom, roomId);
    await RtcRoomController.joinRoomUsingPut(roomId);
  }

  dispose() {
    subscription.cancel();
    pc.close();
  }

  void createOffer() async {
    try {
      await createDataChannel("caller-data-channel", RTCDataChannelInit()..id = room.created_by!);
    } catch (e) {
      print(e); 
    }
    
    await pc.createOffer({}).then((offer) async {
      await pc.setLocalDescription(RTCSessionDescription(offer.sdp, offer.type));
      maybeSendOffer();
    });
  }

  Future maybeSendOffer() async {
    var description = await pc.getLocalDescription();
    if(CallHelper.joinedUserIds.containsKey(room.room_id) && CallHelper.joinedUserIds[room.room_id]?.isNotEmpty == true && description != null) {
      await sendOffer();
    }
  }

  Future sendOffer() async {
    var offer = await pc.getLocalDescription();
    if(offer == null) return;
    var offerJson = jsonEncode(offer.toMap());
    RTCMessageEventBus.sendRtcMessage(RTCMsgType.offer, room.user_id!, room.room_id!, RTCMessageEventBus.gzipEncode(offerJson));
  }

  Future sendAnswer() async {
    var answer = await pc.getLocalDescription();
    if(answer == null) return;
    var answerJson = jsonEncode(answer.toMap());
    RTCMessageEventBus.sendRtcMessage(RTCMsgType.answer, room.user_id!, room.room_id!, RTCMessageEventBus.gzipEncode(answerJson));
  }

  Future createAnswer() async {
    await pc.createAnswer().then((answer) async {
      pc.setLocalDescription(answer);
      await sendAnswer();
    });   
  }

  Future<RTCDataChannel> createDataChannel(channelName, RTCDataChannelInit channelInit) async {
    var channel = await pc.createDataChannel(channelName, channelInit);
    channel.onDataChannelState = (state) {
      dataChannelState = state;
      if(onDataChannelState != null) onDataChannelState!(state);
      if(state == RTCDataChannelState.RTCDataChannelOpen) {
        sendDataChannelMessage('打招呼的消息');
      }
    };
    channel.onMessage = (data) {
      if(onChannelDataMessage!= null) onChannelDataMessage!(data);
      print(data.text);
    };
    dataChannel = channel;
    return channel;
  }

  Future sendDataChannelMessage(String text) async {
    await dataChannel?.send(RTCDataChannelMessage(text));
  }

  Future sendDataChannelBinaryMessage(Uint8List binary) async {
    await dataChannel?.send(RTCDataChannelMessage.fromBinary(binary));
  }
}